#!/usr/bin/env node
/**
 *
 * 项目部署脚本
 *  node deploy.js --env=${env}
 *  env : [dev,test,prod]
 * 自定义请配置env数据
 *  // test.json
*   {
      'env': 'dev',
      'name': '开发服务器',
      'host': '172.16.xxx.xx',
      'username': 'root',
      'password': 'xxxxxx',
      'distDir': '/usr/local/web',
      'distZipName': 'wit-community-huianjia',
      'bakeup': false // 是否备份
    }
 */
    const fs = require('fs')
    const path = require('path')
    const execa = require('execa')
    const inquirer = require('inquirer')
    const minimist = require('minimist')
    const chalk = require('chalk')
    const ora = require('ora')
    const { NodeSSH } = require('node-ssh')
    const zipper = require('zip-local')

    const LOCAL = {
      distDir: 'dist',
      distZip: 'dist.zip'
    }
    const ssh = new NodeSSH()
    const ENV = [
      {
        'env': 'dev',
        'name': '开发服务器',
        'host': '8.130.49.193',
        'username': 'root',
        'password': 'Newjus666',
        'distDir': '/docker/nginx',
        'distZipName': 'html',
        'bakeup': false // 是否备份
      }
    ]
    
    /**
     * 打印提示
     */
    // const textNormal = text => console.log(text)
    const textTitle = text => console.log(chalk.bgCyan(chalk.black(text)))
    const textInfo = text => console.log(chalk.cyan(text))
    // const textSuccess = text => console.log(chalk.green(text))
    const textError = text => console.log(chalk.red(text))
    /**
     * 路径解析
     * @param {*} _path
     * @param {*} _file
     */
    const resolvePath = (_path, _file) => path.resolve(_path, _file)
    
    /**
     * 获取时间
     */
    const getTime = function getTime() {
      const _Date = new Date()
      const date = _Date.toLocaleDateString()
      const time = _Date.toTimeString().split(' ')[0].replace(/\:/g, '-')
      return `${date}_${time}`
    }
    /**
     * 压缩打包好的项目
     * @param {*} next
     */
    const compressDist = async(next) => {
      try {
        const { distDir, distZip } = LOCAL
        const dist = resolvePath(process.cwd(), distDir)
        if (!fs.existsSync(dist)) {
          textError('× 压缩失败')
          textError(`× 打包路径 [${LOCAL.distDir}] 配置错误，${dist} 不存在！\n`)
          process.exit(1)
        }
        const spinner = ora(chalk.cyan('正在压缩...\n')).start()
    
        zipper.sync.zip(dist).compress().save(resolvePath(process.cwd(), distZip))
    
        spinner.succeed(chalk.green('压缩完成！\n'))
        if (next) next()
        return Promise.resolve()
      } catch (err) {
        textError('压缩失败！', err)
      }
    }
    /**
     * 通过 ssh 在服务器上命令
     * @param {*} cmd shell 命令
     * @param {*} cwd 路径
     */
    async function runCommand(cmd, cwd) {
      await ssh.execCommand(cmd, {
        cwd,
        onStderr(chunk) {
          textError(`${cmd}, stderrChunk, ${chunk.toString('utf8')}`)
        }
      })
    }
    
    /**
     * 1、执行构建打包项目命令
     * @param {*} command 命令 string
     * @param {*} params 参数 array
     */
    const build = async(command, params, next) => {
      await execa(command, params, { stdio: 'inherit' })
        .then(() => {
          ora().succeed(chalk.green('打包完成！\n'))
          if (next) next()
          return Promise.resolve()
        })
        .catch(() => {
          textError(`× 打包失败！[script: ${command} ${params}]\n`)
          process.exit(1)
        })
    }
    /**
     * 2、选择部署环境
     * @param {*} env
     */
    const choiceEnv = async() => {
      let _env
      const args = minimist(process.argv)
      if (args.env) {
        _env = args.env
      } else {
        const { env, custom } = await inquirer.prompt([
          {
            name: 'env',
            message: '请选择部署环境：',
            type: 'list',
            choices: [
              { name: '开发', value: 'dev' },
              { name: '测试', value: 'test' },
              { name: '生产', value: 'prod' },
              { name: '自定义', value: 'custom' }
            ]
          },
          {
            name: 'custom',
            message: '请输入配置文件路径：',
            type: 'input',
            when: answers => answers.env === 'custom'
          }
        ])
        if (custom) {
          if (fs.existsSync(custom)) {
            try {
              _env = JSON.parse(fs.readFileSync(custom, 'utf8'))
            } catch (err) {
              textError('× 文件解析错误，请输入正确的数据格式！')
              process.exit(1)
            }
          } else {
            textError('× 文件不存在')
            process.exit(1)
          }
        } else {
          _env = env
        }
      }
      const res = ENV.find(v => _env === v.env)
      if (!res) {
        textError(`× 请选择正确的部署环境\n`)
        process.exit(1)
      } else {
        return res
      }
    }
    
    /**
     * 3、连接服务器
     * @param {*} params { host, username, password }
     */
     const connectServer = async (params) => {
      const spinner = ora(chalk.cyan('正在连接服务器...\n')).start()
      try {
        await ssh.connect(params)
        spinner.succeed(chalk.green('服务器连接成功！\n'))
      } catch (error) {
        spinner.fail(chalk.red('服务器连接失败！\n'))
        textError(err)
        process.exit(1)
      }
    }
    
    const deploy = async() => {
      textTitle('======== 自动部署项目 ========')
      textInfo('')
      const {
        host,
        username,
        password,
        distDir,
        distZipName,
        bakeup } = await choiceEnv()
      await build('npm', ['run', 'build:prod'])
      await compressDist()
      await connectServer({ host, username, password })
      // 部署
      const spinner = ora(chalk.cyan('正在部署项目...\n')).start()
      try {
        // 上传压缩的项目文件
        await ssh.putFile(
          resolvePath(process.cwd(), LOCAL.distZip),
          `${distDir}/${distZipName}.zip`
        )
    
        if (bakeup) {
          // 备份重命名原项目的文件
          await runCommand(
            `mv ${distZipName} ${distZipName}_${getTime()}`,
            distDir
          )
        } else {
          // 删除原项目的文件
          await runCommand(`rm -rf ${distZipName}`, distDir)
        }
        // 修改文件权限
        await runCommand(`chmod 777 ${distZipName}.zip`, distDir)
    
        // 解压缩上传的项目文件
        await runCommand(`unzip ./${distZipName}.zip -d ${distZipName}`, distDir)
    
        // 删除服务器上的压缩的项目文件
        await runCommand(`rm -rf ./${distZipName}.zip`, distDir)
    
        spinner.succeed(chalk.green('部署完成！\n'))
        textInfo(`项目路径: ${distDir}/${distZipName}`)
        textInfo(new Date())
        textInfo('')
        process.exit()
      } catch (err) {
        spinner.fail(chalk.red('项目部署失败！\n'))
        textError(`catch: ${err}`)
        process.exit(1)
      }
    }
    deploy()
    